The core question: how do the "client โ WS server" half and the "chat service โ Pub/Sub" half join into one system? Answer: they form a loop, not a line.
Architecture
The loop, step by step
Inbound โ Alice sends "hi" to room 42
Alice's phone sends the frame over her open WebSocket to the server she's pinned to (WS-1).
LB routed her there originally (sticky, so the long-lived connection stays put).
WS-1 makes an RPC to the Chat Service. This is the connection the two halves share.
Chat Service validates, then persists to the Message DB (history).
Chat Service publishes to Pub/Sub topic room:42.
Outbound โ Bob (room 42) is on a different server WS-2
Every endpoint server holding a member of room 42 is subscribed to room:42. Pub/Sub fans the message out to all of them, including WS-2.
WS-2 pushes the frame down Bob's WebSocket.
The sender's server never needs to know where the other members are connected. That's the whole reason Pub/Sub sits in the middle.
Two corrections to watch for: the client pushes to its WS server, not to Pub/Sub. And the WS server receives updates from Pub/Sub, not directly from the Chat Service. The Chat Service publishes; WS servers subscribe.
Why each piece is there
WebSocket servers are stateful โ they hold the open connection per client. This forces sticky LB routing and makes them the thing that's hard to scale/restart.
Chat Service is stateless โ validate, persist, publish. Scales horizontally, easy to restart.
Pub/Sub decouples fan-out from topology. One publish reaches N servers without the sender knowing who's where. Redis Pub/Sub for simple, Kafka if you need replay/history-of-events.
Message DB holds durable history (Pub/Sub is fire-and-forget; a client that reconnects pulls missed messages from the DB, not the topic).
Follow-ups an interviewer will hit
Subscription bookkeeping: servers subscribe/unsubscribe from room topics as clients join/leave. Where's that state? What happens on WS server crash (connections drop, clients reconnect through LB to a new server, re-subscribe)?
Missed messages: Pub/Sub is at-most-once and ephemeral. On reconnect, client fetches messages since last_seen_id from the DB. Pub/Sub is only for live delivery.
Delivery guarantees / ordering: per-room ordering via a single partition or sequence number per room.
Scale of topics: millions of rooms = millions of topics. Redis Pub/Sub doesn't shard by topic cleanly โ may need Redis Cluster or a partitioned Kafka topic keyed by room ID.
Presence (who's online) is a separate concern, often its own service.